﻿using System.Collections.Generic;
using Godot.scene.main;
using Godot.core;

namespace Godot.scene.resource
{
    public class PackedScene : Resource
    {
        private struct __NodeData
        {
            public int parent;
            public int owner;
            public int type;
            public int name;
            public int instance;

            public struct Property
            {
                public int name;
                public int value;
            };

            public List<__NodeData.Property> properties;
            public List<int> groups;

            public static __NodeData create()
            {
                __NodeData nd = new __NodeData();
                nd.properties = new List<__NodeData.Property>();
                nd.groups = new List<int>();
                return nd;
            }
        };

        private List<StringName> __names;
        private List<__NodeData> __nodes;
        private List<Variant> __variants;

        public Node instance(bool p_gen_edit_state = false)
        {
            int nc = __nodes.Count;
	        if(nc == 0) return null;

	        StringName[] snames = null;
	        int sname_count = __names.Count;

	        Variant props = null;
	        int prop_count = __variants.Count;

	        List<Variant> properties  = new List<Variant>();

	        Node[] ret_nodes = new Node[nc];

	        for(int i = 0; i < nc; i++) 
            {
		        __NodeData n = __nodes[i];

		        if (!ObjectTypeDB.is_type_enabled(snames[n.type])) 
                {
			        ret_nodes[i] = null;
			        continue;
		        }

		        Node node = null;

		        if (n.instance >= 0) 
                {
			        //instance existing
			        PackedScene sdata = (PackedScene)(Resource)__variants[n.instance];
			        
                    if(sdata == null) return null;
			        
                    node = sdata.instance();
			        
                    if(node == null) return null;

			        if (p_gen_edit_state)
				        node.generate_instance_state();

		        } 
                else 
                {
			        //create anew
			        Object obj = ObjectTypeDB.instance(snames[ n.type ]);
			        
                    if(obj == null) return null;
			        
                    node = obj as Node;
			        
                    if(node == null) return null;

		        }


		        //properties
		        int nprop_count = n.properties.Count;
		        if (nprop_count > 0) 
                {
			        for(int j = 0; j < nprop_count; j++) 
                    {
				        bool valid;
				        if(n.properties[j].name >= sname_count) return null;
				        if(n.properties[j].value >= prop_count) return null;

				        node.set(__names[n.properties[j].name], __variants[n.properties[j].value], ref valid);
			        }
		        }

		        //name

		        //groups
		        for(int j = 0; j < n.groups.Count; j++) 
                {
			        if(n.groups[j] >= sname_count) return null;
			        node.add_to_group( __names[ n.groups[j]] , true );
		        }


		        ret_nodes[i] = node;

		        if (i > 0) 
                {			
			        if(n.parent >= i) return null;
			        
                    if(ret_nodes[n.parent] == null) return null;

			        ret_nodes[n.parent]._add_child_nocheck(node,__names[n.name]);
		        } 
                else 
                {
			        node._set_name_nocheck(__names[ n.name ] );
		        }


		        if (n.owner >= 0) 
                {
			        if(n.owner >= i) return null;
			        node._set_owner_nocheck(ret_nodes[n.owner]);
		        }

	        }


	        //do connections

	        int cc = connections.Count;
	        ConnectionData cdata = connections.ptr();

	        for(int i=0;i<cc;i++) {

		        const ConnectionData &c=cdata[i];
		        ERR_FAIL_INDEX_V( c.from, nc, NULL );
		        ERR_FAIL_INDEX_V( c.to, nc, NULL );

		        Vector<Variant> binds;
		        if (c.binds.size()) {
			        binds.resize(c.binds.size());
			        for(int j=0;j<c.binds.size();j++)
				        binds[j]=props[ c.binds[j] ];
		        }

		        if (!ret_nodes[c.from] || !ret_nodes[c.to])
			        continue;
		        ret_nodes[c.from]->connect( snames[ c.signal], ret_nodes[ c.to ], snames[ c.method], binds,CONNECT_PERSIST|c.flags );
	        }

	        Node *s = ret_nodes[0];

	        if (get_path()!="" && get_path().find("::")==-1)
		        s->set_filename(get_path());
	        return ret_nodes[0];
        }
    }
}
